![]() ![]() ![]() ![]() ![]() |
Charlie Calvert's C++ Builder Unleashed
- 28 -
|
Component | Purpose |
THermes | Get into and out of DirectX Exclusive mode |
TScene | Draw the background of a graphics scene |
TSpriteScene | Draw the background of a graphics scene that supports sprites |
THermesChart | Create a tiled game scene |
DirectX is a Microsoft technology that allows you to write high-performance gaming, simulation, and multimedia programs. In this chapter, I focus on the IDirectDraw interface, which is a portion of DirectX that gives you the ability to write directly to the video buffer.
The first thing you find when working with DirectX is that, to take full advantage of it, you need to switch into a special video mode called Exclusive mode. For the purposes of this book, this mode has a 640x480 screen dimension and 256 colors. Actually, other screen sizes and color resolutions are available to you as a DirectX programmer, but THermes gives you access only to this one screen mode.
The first object in the graphics engine is designed to switch you into and out of this mode. The class declaration for this object is shown in Listing 28.1. You can find the full source for the code shown here in a file called Mercury2.cpp in the Utils directory on the CD that accompanies this book.
The Mercury unit relies on Creatures.pas, which ships with this book, and DDraw.h, which ships in the included directory with BCB. You will also find a Pascal version of the Mercury unit in the Units subdirectory on the CD that accompanies this book.
NOTE: To find some Web sites of interest to DirectX programmers and full Pascal translations of DirectX 3.X headers, go to the following:
http://www.dkw.com/bstone/
When you're looking at this code, keep these points in mind:
class THermes : public Classes::TComponent { typedef Classes::TComponent inherited; friend THermesChart; friend TDraw; private: bool FActive; Creatures1::TFileCreatureList* FCreatureList; HWND FHandle; bool FTimerOdd; int FTimerInterval; bool FExclusive; Classes::TNotifyEvent FPaintProc; TScene* FScene; bool FUseTimer; bool FFirstTime; IDirectDraw* FDirectDraw; IDirectDrawSurface* FBackSurface; IDirectDrawClipper* FClipper; IDirectDrawSurface* FPrimarySurface; bool __fastcall CreatePrimary(void); void __fastcall DDTest(long hr, System::AnsiString S); void __fastcall InitBaseObjects(void); bool __fastcall MakeItSo(long DDResult); void __fastcall SetScene(TScene* Scene); bool __fastcall SetUpBack(void); protected: void __fastcall DrawBitmaps(void); virtual long __fastcall RestoreSurfaces(void); public: __fastcall virtual THermes(Classes::TComponent* AOwner); __fastcall virtual ~THermes(void) {} void __fastcall EndExclusive(void); void __fastcall ErrorEvent( System::AnsiString S); void __fastcall Flip(void); virtual void __stdcall InitObjects(void); virtual void __fastcall Run(void); __property bool Active = {read=FActive, write=FActive, nodefault}; __property IDirectDrawSurface* BackSurface = {read=FBackSurface, write=FBackSurface, nodefault}; __property Classes::TNotifyEvent OnDrawBitmap = {read=FPaintProc, write=FPaintProc}; __published: __property Creatures1::TFileCreatureList* CreatureList = {read=FCreatureList, write=FCreatureList, nodefault}; __property bool Exclusive = {read=FExclusive, write=FExclusive, nodefault}; __property TScene* Scene = {read=FScene, write=SetScene, nodefault}; __property int TimerInterval = {read=FTimerInterval, write=FTimerInterval, nodefault}; __property bool UseTimer = {read=FUseTimer, write=FUseTimer, nodefault};
};
To install THermes and the other graphics engine objects, choose Component
| Install. Click the Add button, and then browse the Units directory from
the CD that accompanies this book. Install both Creatures1.pas and Mercury2.cpp.
To use THermes, start a new project, drop a THermes object on it, and create an OnKeyDown handler that closes the form if any key is pressed. The code for such a project is shown in Listings 28.2 and 28.3.
/////////////////////////////////////// // Main.h // Testing the THermes object // Copyright (c) 1997 by Charlie Calvert // #ifndef MainH #define MainH #include <vcl\Classes.hpp> #include <vcl\Controls.hpp> #include <vcl\StdCtrls.hpp> #include <vcl\Forms.hpp> #include "..\..\utils\Mercury2.h" #include <vcl\Menus.hpp> class TForm1 : public TForm { __published: THermes *Hermes1; TMainMenu *MainMenu1; TMenuItem *Run1; void __fastcall FormKeyDown(TObject *Sender, WORD &Key, TShiftState Shift); void __fastcall Run1Click(TObject *Sender); private: void __fastcall MyExceptions(TObject *Sender, Exception *E); public: __fastcall TForm1(TComponent* Owner); }; extern TForm1 *Form1;
#endif
/////////////////////////////////////// // Main.cpp // The TestHermes project tests the THermes component // Copyright (c) 1997 by Charlie Calvert // #include <vcl\vcl.h> #pragma hdrstop #include "Main.h" #pragma link "Mercury2" #pragma resource "*.dfm" TForm1 *Form1; __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { Application->OnException = MyExceptions; } void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key, TShiftState Shift) { if ((Shift.Contains(ssAlt)) && (Key == `E')) throw Exception("Some Exception"); else if (!Shift.Contains(ssAlt)) Close(); } void __fastcall TForm1::Run1Click(TObject *Sender) { Hermes1->Run(); } void __fastcall TForm1::MyExceptions(TObject *Sender, Exception *E) { Hermes1->EndExclusive(); ShowMessage(E->Message);
}
You should note that this project probably will not run correctly unless you add
Creatures.pas to it, as shown in the USEUNIT statement from the
project source:
//-------------------------------------------------------------------------- #include <vcl\vcl.h> #pragma hdrstop //-------------------------------------------------------------------------- USEFORM("Main.cpp", Form1); USERES("HermesTest1.res"); USEUNIT("..\..\Units\creatures1.pas"); //-------------------------------------------------------------------------- WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { try { Application->Initialize(); Application->CreateForm(__classid(TForm1), &Form1); Application->Run(); } catch (Exception &exception) { Application->ShowException(&exception); } return 0; } //--------------------------------------------------------------------------
Run the project once with Exclusive set to False. Click the Run button once to switch on DirectX. In this mode, you should see the color of the main form change, but nothing else very special will happen.
Go back into design mode, and check once to make sure that you have not placed any components on the main form other than the menu and the THermes. The rest of the form must be free of visual controls so that you can detect keystrokes directly on the surface of the form.
Set Exclusive to True and run the program again. Click the Run menu item, and the whole screen should go blank as you switch into Exclusive mode. To get out of the mode, simply press any key.
If you want, you can change the code for the OnKeyDown event:
void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key, TShiftState Shift) { if ((Shift.Contains(ssAlt)) && (Key == `X')) Close(); }
With this code in place, you will be able to press Alt+Tab to move away from the main window of the program and switch out of Exclusive mode. You can then press Alt+Tab to move back to the program and press Alt+X to exit. When you're pressing these keys, the actual picture drawn on your screen when in Exclusive mode is undefined because THermes does not control the output in Exclusive mode; it controls only the act of entering and exiting Exclusive mode.
DirectX allows you to run applications in a windowed mode. That is, you don't have to slip into Exclusive mode to run DirectX applications. However, windowed mode is not very good in terms of performance. In fact, you'll find little advantage to a windowed mode DirectX application over a GDI-based standard Windows program.
I give you access to windowed mode primarily because it is valuable when you're debugging an application. In short, you should run your DirectX applications in windowed mode while you're debugging them so that you can step through the debugger while viewing your application's output. After you set up your application correctly, switch into Exclusive mode and test it.
If an exception happens when you're in Exclusive mode, that is a bad thing. In particular, your program will appear to lock up, and you may be forced to reboot. Following the steps outlined in the preceding paragraphs helps you eliminate the possibility that this will happen. However, you cannot be sure that an exception will not be raised in your application. As a result, you might want to override the exception handler for your application in this case:
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { Application->OnException = MyExceptions; } void __fastcall TForm1::MyExceptions(TObject *Sender, Exception *E) { Hermes1->EndExclusive(); ShowMessage(E->Message); }
The constructor for the main form designates a custom routine called MyExceptions to handle all the exceptions that occur in this application. Whenever an unhandled exception occurs, it will be passed to this routine. When I get the exception, I call Hermes1->EndExclusive, which pops the application out of Exclusive mode, therefore making it possible to open the dialog box that shows the error message.
NOTE: This system will not do you any good, of course, unless you turn off the Break On Exception option, which you can find by choosing Options | Environment | Preferences. If you are in Exclusive mode, and the IDE tries to take you to the line of code in your application where an exception occurred, your system will appear to lock up.
As a rule, I will not show you the underlying Object Pascal code that makes this component work. However, in this one case, I will show you what goes on:
procedure THermes.EndExclusive; begin if (FDirectDraw <> nil) then begin FDirectDraw.FlipToGDISurface; if FExclusive then FDirectDraw.SetCooperativeLevel(FHandle, DDSCL_Normal); end; end;
This code first flips away from DirectX to GDI mode and then cuts out of Exclusive mode by setting the cooperative level to normal. When I take the application into Exclusive mode, I make the same call but with different parameters:
DDTest(DirectDrawCreate(nil, FDirectDraw, nil), `InitObjects1'); if not FExclusive then Flags := DDSCL_NORMAL else Flags := DDSCL_EXCLUSIVE or DDSCL_FULLSCREEN; DDTest(FDirectDraw.SetCooperativeLevel(FHandle, Flags),'SetCooperativeLevel'); if FExclusive then begin hr := FDirectDraw.SetDisplayMode(640, 480, 8); if(hr <> DD_OK) then raise EDDError.CreateFmt(`THermes.InitObjects: %d %s', [hr, GetOleError(hr)]);
end;
Notice that my EndExclusive method makes no attempt to set the display mode back to the dimensions and bit depth selected by the user in his or her system configuration. Instead, I just leave it to the user to close the application, which will return the screen to its normal resolution. My goal in using EndExclusive is just to keep the system from hanging.
After you know how to get into and out of Exclusive mode, the next step is to learn how to draw a picture to the screen. Most games consist of many pictures, but I start out showing you how to use a simple object that just displays a single bitmap. For most people, this object is not really very useful, in that there is no point in using DirectX if you want to show only one picture. However, TScene serves as a base class for other objects that handle more complex tasks. I will therefore spend a few moments showing you how to use it so that you will understand the class on which the other objects are built.
You can find the declaration for the class and some code that uses it in Listings 28.4 through 28.6. Again, I show only the class declarations for the Pascal code and give the complete listings for the C++ code.
class TDraw; class TDraw : public Classes::TComponent { typedef Classes::TComponent inherited; friend THermesTiler; friend THermesChart; friend TScene; friend TSpriteScene; private: AnsiString FDLLName; THermes* FHermes; HANDLE FLib; IDirectDrawPalette* FPalette; int FTransparentColor; bool __fastcall CreateDDSurface(IDirectDrawSurface* &DDS, System::AnsiString BitmapName, bool UsePalette); IDirectDrawSurface* __fastcall CreateSurface(HANDLE Bitmap); HANDLE __fastcall GetDib(HANDLE Instance, System::AnsiString S); IDirectDrawPalette* __fastcall LoadPalette(HANDLE Instance, const System::AnsiString BitmapName); public: __fastcall virtual TDraw(Classes::TComponent* AOwner); __fastcall virtual ~TDraw(void); void __fastcall WriteXY(int X, int Y, System::AnsiString S); __property int TransparentColor = {read=FTransparentColor, write=FTransparentColor, nodefault}; __published: __property AnsiString DLLName = {read=FDLLName, write=FDLLName}; }; class TScene : public TDraw { typedef TDraw inherited; friend THermes; private: System::AnsiString FBackgroundBitmap; Graphics::TColor FBackColor; RECT FBackRect; tagPOINT FBackOrigin; bool FBlankScene; bool FShowBitmap; IDirectDrawSurface* FWorkSurface; Classes::TNotifyEvent FOnSetupSurfaces; Classes::TNotifyEvent FOnDrawScene; public: __fastcall virtual TScene(Classes::TComponent* AOwner); virtual void __fastcall DestroyObjects(void); virtual void __fastcall DrawScene(void); virtual long __fastcall RestoreSurfaces(void); virtual void __fastcall SetupSurfaces(System::TObject* Sender); __published: __property System::AnsiString BackgroundBitmap = {read=FBackgroundBitmap, write=FBackgroundBitmap, nodefault}; __property bool BlankScene = {read=FBlankScene, write=FBlankScene, nodefault}; __property long OriginX ={read=FBackOrigin.x, write=FBackOrigin.x, nodefault}; __property long OriginY ={read=FBackOrigin.y, write=FBackOrigin.y, nodefault}; __property bool ShowBitmap = {read=FShowBitmap, write=FShowBitmap, default=1}; __property Classes::TNotifyEvent OnDrawScene = {read=FOnDrawScene, write=FOnDrawScene}; __property Classes::TNotifyEvent OnSetupSurfaces = {read=FOnSetupSurfaces, write=FOnSetupSurfaces}; __property Graphics::TColor BackColor = {read=FBackColor, write=FBackColor, nodefault}; __property TransparentColor ; public: __fastcall virtual ~TScene(void) { }
};
/////////////////////////////////////// // Main.h // Test the TScene object // Copyright (c) 1997 by Charlie Calvert // #ifndef MainH #define MainH #include <vcl\Classes.hpp> #include <vcl\Controls.hpp> #include <vcl\StdCtrls.hpp> #include <vcl\Forms.hpp> #include <vcl\Menus.hpp> #include "Mercury2.h" class TForm1 : public TForm { __published: TScene *Scene1; THermes *Hermes1; TMainMenu *MainMenu1; TMenuItem *Run1; void __fastcall Run1Click(TObject *Sender); void __fastcall FormKeyDown(TObject *Sender, WORD &Key, TShiftState Shift); private: public: __fastcall TForm1(TComponent* Owner); }; extern TForm1 *Form1;
#endif
/////////////////////////////////////// // Main.cpp // Test the TScene object // Copyright (c) 1997 by Charlie Calvert // #include <vcl\vcl.h> #pragma hdrstop #include "Main.h" #pragma link "Mercury2" #pragma resource "*.dfm" TForm1 *Form1; __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } void __fastcall TForm1::Run1Click(TObject *Sender) { Hermes1->Run(); } void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key, TShiftState Shift) { if ((Shift.Contains(ssAlt)) && (Key == `X')) Close();
}
The program shown here can switch you into Exclusive mode and show a picture
like the one shown in Figure 28.1.
Figure 28.1. The main screen of the TestScenel program in windowed mode.
To get started with the TScene object, drop both it and the THermes object on a form. Use the Scene property of THermes to connect it to the TScene object. Click the TScene object, and select a background bitmap by browsing the Chap28/Media directory included on the CD that accompanies this book. Select any bitmap that is smaller than or equal to 640x480 and that has 256 colors in it. For example, you might choose BackGrd2.bmp. Set the Transparent color to black, which will appear at offset zero on the bitmaps. Set the BackColor property of the bitmap to the same shade, and set BlankScene to True:
TransparentColor = 0; BackColor = clBlack; BlankScene = True;
NOTE: TransparentColor refers to the color in a bitmap that will be transparent. That is, the areas in the bitmap where this color is present become transparent. You need a transparent color when you want to blit an irregularly shaped bitmap on top of a background bitmap. For example, if you have a picture of a bird that you want to blit onto a picture of the sky, just set the background area in the picture of the bird to the transparent color, and it will not show up when the bird is blitted to the screen. In other words, the picture of the sky will show through the transparent area, so the bird will appear directly against the sky, with no intervening border.
You choose the selected transparent color from an offset in the palette of the bitmap you're using. For example, if you set TransparentColor to 0, the first color in the bitmap's palette will be the transparent color. Most good paint programs will show you the palette for a picture. For example, Paint Shop Pro provides this service.
If you select a prominently used color from a picture as the transparent color, the portions of the picture with that color in them will look strange at runtime. To avoid this situation, set the BackColor property of TScene and the TransparentColor for the picture to the same shade. Also set BlankScene to True. This way, the transparent color will look through onto a background that is the same shade as the color you're making transparent.
The BlankScene property blanks the background of the TScene object to the color found in the BackColor property. This capability can be useful in the current situation and also when you have a small bitmap that you want to show against a blank window set to some particular color.
When you're creating multimedia applications in Windows, the palette of your bitmaps is very important. In particular, you should usually create the same 256-color palette for all your bitmaps. Use a program such as Paint Shop Pro to set up these bitmaps. Most of the bitmaps included in the Chap28/Media directory on the CD that comes with this book have a palette designed for pictures that have lots of green fields, brown earth, and a range of skin tones.
When you're working with only one bitmap in a scene, all this business about palettes seems unnecessarily complicated. However, after you start adding sprites to the scene, you will be glad that you have some way to set up background colors and to set up TransparentColor. I explain more about palettes in the next section of this chapter.
The TScene object also has a built-in timer that will continually redraw the picture at preset intervals. This capability is not particularly useful in the case of a program that blits only one picture to the screen. However, when you start working with multiple bitmaps, some of which are animated, this capability becomes a key feature. In particular, DirectX allows you to draw to an offscreen buffer, which is then blitted to the screen almost instantly. Using this method is an excellent way to achieve smooth animation.
Instead of using a timer, you can also handle the TApplication::OnIdle event, and then call Hermes1->Flip inside that event handler. The OnIdle event will be called whenever the CPU is not otherwise occupied.
To get the TScene object set up correctly, you should turn on the timer and set the TimerInterval property to some reasonable value, such as 250 milliseconds. Setting this property will cause the picture to be refreshed four times a second. This number is too slow for animation, but it works fine for the simple scene created here. In particular, if you press Alt+Tab to move away from the DirectX program, the timer will automatically restore the picture when you press Alt+Tab to move back to the main scene.
On the CD that accompanies this book, you will find a program called TestScene1. The source for this program is shown in Listings 28.5 and 28.6. You can use this code as a guide for using the TScene object. Remember that TScene is not really meant to be used in a real program. It is just a base object from which other, more useful objects can descend.
In Windows and DOS games, you almost always work with a 256-color palette. Someday this will change, but for now you should get used to working with these palettes and come to understand them well. An in-depth description of palettes is available in some of the books and Web sites mentioned in the section called "Games Resources" at the beginning of this chapter.
Using Paint Shop Pro, you can save a palette to disk as a text file:
JASC-PAL 0100 256 0 0 0 128 0 0 0 128 0 128 128 0 0 0 128 128 0 128 0 128 128 192 192 192 176 136 72 216 192 160 ... // Many numbers omitted here... 240 224 208 160 160 164 128 128 128 255 0 0 0 255 0 255 255 0 0 0 255 255 0 255 0 255 255 255 255 255
Here is how to decode the first few lines of this file:
JASC-PAL 0100 256 0 0 0
JASC-PAL identifies the palette as belonging to Paint Shop Pro. The second line defines the number of entries in the palette in hexadecimal format. The third line has the number of entries in base ten notation. The fourth line is the first member of the palette in RGB format, with all values set to 0, which is black.
Notice that my palette runs through various shades:
118 102 237 117 116 240 129 113 249 155 110 239 107 144 140 115 144 140 123 136 140 123 148 140 111 148 148
Clearly, I'm interested in this area of the palette.
Obviously, you don't want to have to work with raw numbers like this. Paint Shop Pro will let you view a palette as an array of colors, as shown in Figure 28.2. Having some kind of tool that will help you design your palette is essential. You should also find books, such as this one and the ones mentioned previously, that include sample palettes.
Figure 28.2. Using Paint Shop Pro to view an array of shades that make up a 256-color palette.
Now that you know how to show a simple picture in DirectX mode, and now that you know a bit about palettes, you're ready to start working with sprites. The sprite support I have at this time is very rudimentary, as you can see in Listings 28.7 through 28.9. You could, however, use these base sprite classes to create sprites that are more full-featured. You should also check my Web site for updates to this code.
class TSprite : public Classes::TComponent { typedef Classes::TComponent inherited; friend TSpriteScene; private: System::AnsiString FBitmap; tagPOINT FPosition; IDirectDrawSurface* FSurface; RECT FRect; public: bool __fastcall IsHit(int X, int Y); __property IDirectDrawSurface* Surface= {read=FSurface, write=FSurface, nodefault}; __property RECT Rect = {read=FRect, write=FRect}; __published: __property System::AnsiString Bitmap={read=FBitmap, write=FBitmap, nodefault}; __property long XPos = {read=FPosition.x, write=FPosition.x, nodefault}; __property long YPos = {read=FPosition.y, write=FPosition.y, nodefault}; public: __fastcall virtual TSprite(Classes::TComponent* AOwner) : Classes::TComponent(AOwner) { } __fastcall virtual ~TSprite(void) { } }; class TSpriteScene : public TScene { typedef TScene inherited; private: Classes::TList* FSpriteList; protected: __fastcall virtual ~TSpriteScene(void); virtual void __fastcall SetupSurfaces(System::TObject* Sender); public: __fastcall virtual TSpriteScene(Classes::TComponent* AOwner); virtual void __fastcall DestroyObjects(void); void __fastcall AddSprite(TSprite* Sprite); virtual void __fastcall DrawScene(void); virtual long __fastcall RestoreSurfaces(void); __property Classes::TList* SpriteList = {read=FSpriteList, write=FSpriteList, nodefault}; __published: __property TransparentColor;
};
/////////////////////////////////////// // Main.h // Project: SpriteTest1 // Copyright (c) 1997 by Charlie Calvert // #ifndef MainH #define MainH #include <vcl\Classes.hpp> #include <vcl\Controls.hpp> #include <vcl\StdCtrls.hpp> #include <vcl\Forms.hpp> #include <vcl\Menus.hpp> #include "Mercury2.h" class TForm1 : public TForm { __published: THermes *Hermes1; TSpriteScene *SpriteScene1; TSprite *Sprite1; TSprite *Sprite2; TMainMenu *MainMenu1; TMenuItem *Run1; void __fastcall SpriteScene1SetupSurfaces(TObject *Sender); void __fastcall Run1Click(TObject *Sender); void __fastcall FormKeyDown(TObject *Sender, WORD &Key, TShiftState Shift); private: public: __fastcall TForm1(TComponent* Owner); }; extern TForm1 *Form1;
#endif
/////////////////////////////////////// // Main.cpp // Project: SpriteTest1 // Copyright (c) 1997 by Charlie Calvert // #include <vcl\vcl.h> #pragma hdrstop #include "Main.h" #pragma link "Mercury2" #pragma resource "*.dfm" TForm1 *Form1; __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } void __fastcall TForm1::SpriteScene1SetupSurfaces(TObject *Sender) { SpriteScene1->SpriteList->Add(Sprite1); SpriteScene1->SpriteList->Add(Sprite2); } void __fastcall TForm1::Run1Click(TObject *Sender) { Hermes1->Run(); } void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key, TShiftState Shift) { if ((Shift.Contains(ssAlt)) && (Key == `X')) Close(); }
Don't forget that when using this program, you will probably have to explicitly add Creatures1.pas to your project:
//-------------------------------------------------------------------------- #include <vcl\vcl.h> #pragma hdrstop //-------------------------------------------------------------------------- USEFORM("Main.cpp", Form1); USERES("SpriteTest1.res"); USEUNIT("..\..\Units\creatures1.pas"); //-------------------------------------------------------------------------- WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { try { Application->Initialize(); Application->CreateForm(__classid(TForm1), &Form1); Application->Run(); } catch (Exception &exception) { Application->ShowException(&exception); } return 0; }
Figure 28.3 shows the TestSpriteScene program in action. In this program, I make no attempt to animate the sprites. To do so, you can merely change the x- and y-coordinates at which they are shown and then wait for the screen to be updated automatically by the timer.
Figure 28.3. The TestSpriteScene program shows a background bitmap with two sprites placed on it.
To work with the TSpriteScene object, you should drop a THermes, a TSpriteScene, and one or more TSprite objects on a form. Connect the THermes object to the sprite scene. Pick a background bitmap and a transparent color for the sprite scene. In the sample program, I use BackGrd2.bmp for the background and 254 for the transparent color.
For the bitmaps that ship in the Chap28/Media directory, I assume that the background color is the 254 element in the palette of the background bitmap. This color is a robin's egg blue, which means that this particular shade of blue cannot be used in any of the bitmaps except for areas that you want to be transparent. Don't forget that if a particular RGB pattern such as 255, 0, 255 is designated as the transparent color, you can change any one value to get a nearby color that is not going to appear as transparent. For example, you can choose 255, 1, 255, which is virtually identical in shade to 255, 0, 255, but it will not be transparent to the user and can be shown in your bitmaps.
Connect the TSprite objects to a bitmap. I connect the first sprite to Queen2.bmp and the second sprite to Monk2.bmp.
NOTE: These graphical figures were created by Kari Marcussen and, like all the artwork in this book, are copyrighted and cannot be used in your own programs. You can, of course, use the art in the "privacy of your own home," but you cannot distribute programs of any kind, even free programs, that contain this art.
The XPos and YPos positions for the first sprite are 100x200, whereas the second sprite is 400x200. You don't have to be concerned with the size of the sprite itself, as the components will calculate the size automatically at runtime.
You need to have some way of telling the TSpriteScene object about the sprites that it owns. I use a simple event handler of TSpriteScene called OnSetupSurface for this purpose:
void __fastcall TForm1::SpriteScene1SetupSurfaces(TObject *Sender) { SpriteScene1->SpriteList->Add(Sprite1); SpriteScene1->SpriteList->Add(Sprite2); }
SpriteList is a simple TList descendant. Keeping all the child sprites in a list allows for easy deallocation of memory:
void __fastcall TSpriteScene::DestroyObjects(void) { if(FSpriteList != NULL) { for(int i = 0; i < FSpriteList->Count; i++) ((TSprite*)(FSpriteList->Items[i]))->Surface->Release(); FSpriteList->Clear(); } }
This kind of routine is important because you might want to switch between numerous TScene descendants during the course of a program. In other words, your game may have more than one scene in it. I will explain this process in more depth later in the chapter. However, the quick overview is that you can simply write code that looks like this:
Hermes1->Scene = Scene1; ... // Code omitted here. Hermes1->Scene = Scene2;
In this case, the program starts by showing Scene1 and then at some later point switches to Scene2. When you switch scenes, the DestroyObjects method for the previous scene is called, thereby deallocating all the memory associated with background and sprite bitmaps. At the same time, new memory is allocated for the bitmaps used in Scene2.
The SpriteList also is important when you switch away from a program in Exclusive mode and then press Alt+Tab to move back to it. At those times, the following routine is called:
long __fastcall TSpriteScene::RestoreSurfaces(void) { TSprite *Sprite; HRESULT Result; Result = TScene::RestoreSurfaces(); if(Result == DD_OK) { for(int i = 0; i < FSpriteList->Count; i++) { Sprite = (TSprite *)FSpriteList->Items[i]; Result = Sprite->Surface->Restore(); if(Result == DD_OK) DDReloadBitmapLib(FLib, Sprite->Surface, Sprite->Bitmap); else break; // Exit on error }
}
This routine iterates through all the bitmaps in the SpriteList and restores each surface. This process occurs so quickly that the user simply sees the scene being restored all at once and is not aware that a list of objects is being re-created.
You can now run the program, trying it first in windowed mode, where the output will appear a bit muddy because the transparent color probably won't work correctly. After you're sure everything is running correctly, you can try the program in Exclusive mode. When in Exclusive mode, you can press Alt+Tab to move away from the main game window and view some other program such as the Windows Explorer. You can then press Alt+Tab to go back to the game window. This capability is one of the key features of DirectX.
If you're concerned about the TransparentColor not working right in windowed mode, you can try using a common color such as black for your TransparentColor. It might work correctly, or you can play with the Windows palette system to make sure the right palette is selected when your program appears in a window. For now, however, my graphics engine assumes that you want to run your program in Exclusive mode, and support for windowed mode is available only so that you can easily debug your programs. After all, DirectX doesn't really provide many advantages over GDI in windowed mode. The whole point of this process is to switch into Exclusive mode where you can get the following:
When I say that DirectX offers high performance, I mean that in Exclusive mode you can write directly to the video buffer, thereby obtaining the same kind of performance you would expect from a DOS program. The actual degree of performance improvement you get is dependent on the amount of RAM on your video card and on the bus between RAM and your video card. The ideal situation is to have a video card with enough RAM in it to hold all the bitmaps used by any one scene. Therefore, 2MB of memory on your video card is pretty much a minimum, and a strong argument can be made in favor of having 4MB. The code that ships with this book will work fine, however, with a small amount of RAM such as 524KB.
The actual system for drawing to the video buffer involves a process called flipping. When using this technology, the program draws the image you want to show the user to an offscreen buffer and then flips that offscreen buffer into the part of video memory the user sees. The user therefore never sees the relatively lengthy process of drawing to the buffer but sees only the finished picture when it is blitted, or "flipped," onto the screen. The core job of the graphics engine presented in this chapter is to automate this process so you don't have to think about it.
Some of the most interesting games consist of large worlds that fill up screen after screen with information. Classic examples of this kind of game are Heroes of Might and Magic, WarCraft, and Civilization. The THermesChart component shows how to work with one of these worlds.
Tiled worlds are made up of bitmaps that consist of lots of tiny tiles that can be combined in various ways to create maps. A picture of one of the tiled bitmaps is shown in Figure 28.4, and a small portion of the world created from these tiles is shown in Figure 28.5.
Figure 28.4. The tiles from which a tiled world is made.
Figure 28.5. A tiled world created by the THermesChart component from the bitmaps shown in Figure 28.4.
The declaration for the THermesChart component is shown in Listing 28.10, although the test program for it is shown in Listings 28.11 and 28.12.
struct TSpecialRect { bool IsCreature; RECT R1; RECT R2; }; class THermesTiler : public TScene { typedef TScene inherited; friend THermesChart; private: int FMaxMapRows; int FMaxMapCols; int FBitmapWidth; int FBitmapHeight; System::AnsiString FTileMap; IDirectDrawSurface* FTileSurface; protected: __fastcall virtual ~THermesTiler(void); virtual void __fastcall DrawScene(void); virtual void __fastcall SetupSurfaces(System::TObject* Sender); virtual TSpecialRect __fastcall GetRect(int Col, int Row) = 0; virtual bool __fastcall MoveGrid(int Col, int Row, bool CallFlip) = 0; public: __fastcall virtual THermesTiler(Classes::TComponent* AOwner); virtual void __fastcall DestroyObjects(void); Windows::TRect __fastcall MapTypeToTileRect(int MapType); virtual long __fastcall RestoreSurfaces(void); __published: __property System::AnsiString TileMap = {read=FTileMap, write=FTileMap, nodefault}; __property int BitmapWidth = {read=FBitmapWidth, write=FBitmapHeight, nodefault}; __property int BitmapHeight = {read=FBitmapHeight, write=FBitmapHeight, nodefault}; }; typedef void __fastcall (__closure *TMoveHeroEvent)(System::TObject* Sender, const tagPOINT &NewPos, int NewType, bool &MoveOk); class THermesChart : public THermesTiler { typedef THermesTiler inherited; private: Creatures1::TCreature* FHero; bool FHeroActive; TMoveHeroEvent FOnHeroMove; bool __fastcall CheckHeroPos(Creatures1::TCreatureList* HeroList, int Col, int Row); Windows::TRect __fastcall MapTypeToCreature(int Col, int Row); virtual TSpecialRect __fastcall GetRect(int Col, int Row); virtual bool __fastcall MoveGrid(int Col, int Row, bool CallFlip); protected: void __fastcall MoveHero(int NewCol, int NewRow); virtual void __fastcall SetupSurfaces(System::TObject* Sender); public: __fastcall virtual ~THermesChart(void); void __fastcall Move(int Value); __published: __property bool HeroActive = {read=FHeroActive, write=FHeroActive, nodefault}; __property TMoveHeroEvent OnHeroMove = {read=FOnHeroMove, write=FOnHeroMove}; public: __fastcall virtual THermesChart(Classes::TComponent* AOwner) : Mercury2::THermesTiler(AOwner) { }
};
/////////////////////////////////////// // Main.h // Project: TilerTest1 // Copyright (c) 1997 by Charlie Calvert // #ifndef MainH #define MainH #include <vcl\Classes.hpp> #include <vcl\Controls.hpp> #include <vcl\StdCtrls.hpp> #include <vcl\Forms.hpp> #include "Creatures1.hpp" #include <vcl\Menus.hpp> #include "Mercury2.h" class TForm1 : public TForm { __published: THermes *Hermes1; THermesChart *HermesChart1; TFileCreatureList *FileCreatureList1; TMainMenu *MainMenu1; TMenuItem *Run1; void __fastcall Run1Click(TObject *Sender); void __fastcall FormKeyDown(TObject *Sender, WORD &Key, TShiftState Shift); private: public: __fastcall TForm1(TComponent* Owner); }; extern TForm1 *Form1;
#endif
/////////////////////////////////////// // Main.cpp // Project: TilerTest1 // Copyright (c) 1997 by Charlie Calvert // #include <vcl\vcl.h> #pragma hdrstop #include "Main.h" #pragma link "Creatures1" #pragma link "Mercury2" #pragma resource "*.dfm" TForm1 *Form1; __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } void __fastcall TForm1::Run1Click(TObject *Sender) { Hermes1->Run(); } void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key, TShiftState Shift) { if (Shift.Contains(ssAlt) && (Key == `X')) Close(); else HermesChart1->Move(Key);
}
By now, you should be getting used to the fact that these components are very easy
to use. The only custom code you have to write is to define how to exit the program:
if (Shift.Contains(ssAlt) && (Key == `X')) Close();
Other than this one simple statement, all the other "coding" involves nothing more than changing a few properties.
This particular example uses bitmaps that are stored in a DLL called BitDll.dll. To create a DLL of this type, simply build a resource file containing the bitmaps you want to use and then add the RC file to a DLL project. You can create a DLL by choosing File | New | DLL from the BCB menu.
Here is the RC code for a sample resource that contains multiple bitmaps:
Back BITMAP "PANEL4.BMP" TileMap BITMAP "TILEMAP.BMP" City BITMAP "FLOOR1.BMP" Dirs BITMAP "COMPDIRS.BMP" Treasure BITMAP "TREASURE.BMP" Sage1 BITMAP "SAGE1.BMP"
You can access the various bitmaps in the DLL by name. For example, you should set the BackgroundBitmap property of the THermesChart to Back and the TileMap property to TileMap. As long as the DLLName property is pointing to a valid DLL, you need do nothing else. All TScene descendants know how to read bitmaps from either a file on disk or from a DLL.
You'll discover several obvious advantages to using a DLL rather than a raw BMP file:
The tiled world that you create depends on two interrelated binary files. One binary file contains the screen map that you create, and the second contains a list of creatures that inhabit the map.
The screen map is simply an array 255x252 characters wide. At this time, the map you make must be this size, though I will provide alternative sizes at a later date. Check my Web site for updates.
Each element in the bitmap containing the tiles has a number associated with it. These numbers are described in the following enumerated type:
enum TMapType {mtGrass, mtWater, mtMountain, mtRoad, mtWater2, mtFootHill, mtNorthShore, mtWestShore, SouthShore, mtEastShore, mtSWShore, mtSEShore, mtNWShore, mtNEShore, mtWNWShore, mtWSEShore, mtESEShore, mtENEShore, mtBlank1, mtBlank2, mtBlank3, mtBlank4, mtAllSnow, mtSnowyMountain, mtSouthMtn, mtWestMtn, mtNorthMtn, mtEastMtn, mtSEMtn, mtSWMtn, mtNWMtn, mtNEMtn, mtNWFootHill, mtNEFootHill, mtSEFootHill, mtSWFootHill, mtNorthFootHill, mtEastFootHill, mtSouthFootHill, mtWestFootHill, mtNEDiagShore, mtSEDiagShore, mtSWDiagShore, mtNWDiagShore, mtSWBendShore, mtSEBendShore, mtNWBendShore, mtNEBendShore, mtENBendShore, mtWNBendShore, mtWSBendShore, mtESBendShore, mtCity, mtCreature};
mtGrass is element 0 in this enumerated type. mtWater is element 1; mtMountain, element 2; and so on. This type is not used in the TestTiler1 program, but it will come in handy later in the chapter.
Here is a simple two-dimensional array that encodes a small world:
1 1 1 1 1 1 1 0 1 1 1 0 2 0 1 1 1 0 1 1 1 1 1 1 1
This world consists of a small island with a mountain in the center of it. The entire island is surrounded by water. Of course, this world is tiny and cannot be used by the THermesChart component. To find a world that can be used by THermesChart, look in the Media directory on the CD that accompanies this book. There you will find a textual representation of a world in a file called Screen.txt and binary translation of that file called Screen.dta.
A simple program called TextToBinary found on the CD will translate the textual representation of a world to a binary file. The code that does so takes only a few lines:
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { FMapPointList = new TMapPointList(); } void __fastcall TForm1::FormDestroy(TObject *Sender) { FMapPointList->Free(); } void __fastcall TForm1::Button1Click(TObject *Sender) { int X, Y; if (OpenDialog1->Execute()) { FMapPointList->ReadBinary(OpenDialog1->FileName, X, Y); AnsiString S = ChangeFileExt(OpenDialog1->FileName, ".txt"); FMapPointList->WriteText(S, X, Y); } } void __fastcall TForm1::Button2Click(TObject *Sender) { if (OpenDialog1->Execute()) { FMapPointList->ReadText(OpenDialog1->FileName); AnsiString S = ChangeFileExt(OpenDialog1->FileName, ".dta"); FMapPointList->WriteBinary(S, FMapPointList->StartX, FMapPointList->StartY); } }
The constructor and destructor for this program create an object of type TMapPointList. You can find the TMapPointList object in a file called Creatures1.pas on the CD. To translate a binary file to text, simply call the methods of TMapPointList called ReadBinary and WriteText. To reverse the process, call ReadText and WriteBinary.
NOTE: I don't really expect you to create a world by editing a text file. The file is not ready at the time of this writing, but the CD that ships with this book may contain a program in the Chap28 directory that will allow you to edit a map from inside Windows by using the mouse. Check my Web site for updates to this program.
In particular, note that there are methods in Creatures1.pas that make it easy to create this type of program. For instance, note the following methods of TCreatureList:function NameFromLocation(ACol, ARow: Integer): string; function TypeFromLocation(ACol, ARow: Integer): string; function CreatureFromLocation(ACol: Integer; ARow: Integer): TCreature;
- Methods like this allow you to quickly identify the tile at a particular location. You can then use the Map array property from the same object to change these tiles.
Besides the screen file, you also need to maintain a file that tracks the creatures shown on your tiled world. In the sample programs that ship with this book, this file is called Creatures.dta.
You can edit Creatures.dta with a simple program called Entity. Most of this program is written in Pascal, so I won't discuss its code here. However, it compiles fine in C++Builder.
NOTE: I apologize for including so much Pascal code in this chapter. For various reasons, I need to have these objects working in both C++Builder and Delphi, and the simplest way to do that is to write the core objects and utilities in Object Pascal. In this particular case, I have taken a Delphi program and swapped out the Pascal version of the project source and substituted a version written in BCB. That way, I could get the program to compile in C++Builder by merely changing a few lines of code, all of which are generated automatically by the IDE. In short, I started a new project, removed the main form, and added the main form from the Pascal version of the Entities program. That was all I had to do to translate the program from Delphi to C++Builder.
The Entity program tracks the creature and screen file that it edits through the Registry. To do so, it uses the same technique I discussed in Chapter 13, "Flat-File, Real-World Databases." In particular, the following code should jog your memory as to how I retrieve the current creature and screen file from the Registry:
RegFile := TRegIniFile.Create(`Software\Charlie''s Stuff\Entities'); FCreatureFile := RegFile.ReadString(`FileNames', `CreatureFile', `'); FScreenFile := RegFile.ReadString(`FileNames', `ScreenFile', `');
Running the program once places the proper entries in the Registry. You can then edit the Registry to enter in the names of the actual files you want to use. In particular, you should use the Windows utility called RegEdit.exe to edit the area under HKEY_CURRENT_USER\Charlie's Stuff\Entities.
The creatures file contains a number of creature profiles, as shown in Figure 28.6 and Figure 28.7.
Figure 28.6. The hero's profile as shown in the Entity program.
Figure 28.7. A hostile creature's profile as shown in the Entity program.
When you first open the screen, be sure that the hero is located at column 11 and row 11. To set this up properly, you need to edit the Col and Row fields, as well as the Scrn Col, Scrn Row, Map Col, and Map Row. You need all these values because you can never see the whole map at one time. Instead, you see a window into the entire grid. You therefore need to track where the window is on the grid, where the character is on the grid, and where the character is on the window currently opened onto the grid. You have three pairs of values, all of which are tracked for you automatically by the THermesChart object.
Now that you've examined the screen and creature files, you're ready to run the TestTiler program. Go ahead and run it once in windowed mode to make sure that you're reaching all the files in question. Then run the program again in Exclusive mode.
NOTE: You can possibly mangle the screen file by exiting the TilerTest1 program incorrectly. In other words, if TilerTest1 crashes in mid-run and you reboot, you can lose the contents of the screen file. To check whether you've lost the contents, check that your screen file is equal to 64,276 bytes. If it is some other value, the file is probably corrupt and should be refreshed from the file on the CD that accompanies the book. If you have this problem, you will probably get a Range error when trying to run the program.
The only point left for you to learn is how to move the character on the screen and how to scroll around through the world. To do so, simply modify the OnKeyDown event for TilerTest1:
void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key, TShiftState Shift) { if (Shift.Contains(ssAlt) && (Key == `X')) Close(); else HermesChart1->Move(Key); }
This code will exit the program if the user presses Alt+X; otherwise, it will pass the current keystroke on the HermesChart object. This object responds to presses on the arrow keys by moving either the main character or by scrolling the entire map. Press the Insert key to switch between these two modes. Figure 28.8 shows the main character walking on the map.
Figure 28.8.The main character exploring the shore of a lake, with mountains nearby.
The source for Mercury2.cpp is shown in Listings 28.13 and 28.14. Please note that several stand-alone functions right at the top of Mercury2.cpp are only slightly modified versions of code that appears in the DDUtils.cpp file that ships with the DirectDraw SDK. I could not let you use the original functions, available on the Microsoft Web site, because they do not take into account the possibility that the user might want to store bitmaps in a DLL.
#ifndef Mercury2H #define Mercury2H #include <Creatures1.hpp> #include <ddraw.h> #include <DsgnIntf.hpp> #include <OLE2.hpp> #include <Graphics.hpp> #include <Forms.hpp> #include <Controls.hpp> #include <Classes.hpp> #include <Windows.hpp> #include <System.hpp> namespace Mercury2 { //-- type declarations ------------------------------------------------------ typedef tagPALETTEENTRY T256PalEntry[256]; typedef tagRGBQUAD TRGB[256]; typedef TRGB *PRGB; class THermes; class TScene; class THermesTiler; class THermesChart; class TSpriteScene; class TDraw; class TDraw : public Classes::TComponent { typedef Classes::TComponent inherited; friend THermesTiler; friend THermesChart; friend TScene; friend TSpriteScene; private: AnsiString FDLLName; THermes* FHermes; HANDLE FLib; IDirectDrawPalette* FPalette; int FTransparentColor; bool __fastcall CreateDDSurface(IDirectDrawSurface* &DDS, System::AnsiString BitmapName, bool UsePalette); IDirectDrawSurface* __fastcall CreateSurface(HANDLE Bitmap); HANDLE __fastcall GetDib(HANDLE Instance, System::AnsiString S); IDirectDrawPalette* __fastcall LoadPalette(HANDLE Instance, const System::AnsiString BitmapName); public: __fastcall virtual TDraw(Classes::TComponent* AOwner); __fastcall virtual ~TDraw(void); void __fastcall WriteXY(int X, int Y, System::AnsiString S); __property int TransparentColor = {read=FTransparentColor, write=FTransparentColor, nodefault}; __published: __property AnsiString DLLName = {read=FDLLName, write=FDLLName}; }; class TScene : public TDraw { typedef TDraw inherited; friend THermes; private: System::AnsiString FBackgroundBitmap; Graphics::TColor FBackColor; RECT FBackRect; tagPOINT FBackOrigin; bool FBlankScene; bool FShowBitmap; IDirectDrawSurface* FWorkSurface; Classes::TNotifyEvent FOnSetupSurfaces; Classes::TNotifyEvent FOnDrawScene; public: __fastcall virtual TScene(Classes::TComponent* AOwner); virtual void __fastcall DestroyObjects(void); virtual void __fastcall DrawScene(void); virtual long __fastcall RestoreSurfaces(void); virtual void __fastcall SetupSurfaces(System::TObject* Sender); __published: __property System::AnsiString BackgroundBitmap = {read=FBackgroundBitmap, write=FBackgroundBitmap, nodefault}; __property bool BlankScene = {read=FBlankScene, write=FBlankScene, nodefault}; __property long OriginX ={read=FBackOrigin.x, write=FBackOrigin.x, nodefault}; __property long OriginY ={read=FBackOrigin.y, write=FBackOrigin.y, nodefault}; __property bool ShowBitmap = {read=FShowBitmap, write=FShowBitmap, default=1}; __property Classes::TNotifyEvent OnDrawScene = {read=FOnDrawScene, write=FOnDrawScene}; __property Classes::TNotifyEvent OnSetupSurfaces = {read=FOnSetupSurfaces, write=FOnSetupSurfaces}; __property Graphics::TColor BackColor = {read=FBackColor, write=FBackColor, nodefault}; __property TransparentColor ; public: __fastcall virtual ~TScene(void) { } }; class THermes : public Classes::TComponent { typedef Classes::TComponent inherited; friend THermesChart; friend TDraw; private: bool FActive; Creatures1::TFileCreatureList* FCreatureList; HWND FHandle; bool FTimerOdd; int FTimerInterval; bool FExclusive; Classes::TNotifyEvent FPaintProc; TScene* FScene; bool FUseTimer; bool FFirstTime; IDirectDraw* FDirectDraw; IDirectDrawSurface* FBackSurface; IDirectDrawClipper* FClipper; IDirectDrawSurface* FPrimarySurface; bool __fastcall CreatePrimary(void); void __fastcall DDTest(long hr, System::AnsiString S); void __fastcall InitBaseObjects(void); bool __fastcall MakeItSo(long DDResult); void __fastcall SetScene(TScene* Scene); bool __fastcall SetUpBack(void); protected: void __fastcall DrawBitmaps(void); virtual long __fastcall RestoreSurfaces(void); public: __fastcall virtual THermes(Classes::TComponent* AOwner); __fastcall virtual ~THermes(void) {} void __fastcall EndExclusive(void); void __fastcall ErrorEvent( System::AnsiString S); void __fastcall Flip(void); virtual void __stdcall InitObjects(void); virtual void __fastcall Run(void); __property bool Active = {read=FActive, write=FActive, nodefault}; __property IDirectDrawSurface* BackSurface = {read=FBackSurface, write=FBackSurface, nodefault}; __property Classes::TNotifyEvent OnDrawBitmap = {read=FPaintProc, write=FPaintProc}; __published: __property Creatures1::TFileCreatureList* CreatureList = {read=FCreatureList, write=FCreatureList, nodefault}; __property bool Exclusive = {read=FExclusive, write=FExclusive, nodefault}; __property TScene* Scene = {read=FScene, write=SetScene, nodefault}; __property int TimerInterval = {read=FTimerInterval, write=FTimerInterval, nodefault}; __property bool UseTimer = {read=FUseTimer, write=FUseTimer, nodefault}; }; struct TSpecialRect { bool IsCreature; RECT R1; RECT R2; }; class THermesTiler : public TScene { typedef TScene inherited; friend THermesChart; private: int FMaxMapRows; int FMaxMapCols; int FBitmapWidth; int FBitmapHeight; System::AnsiString FTileMap; IDirectDrawSurface* FTileSurface; protected: __fastcall virtual ~THermesTiler(void); virtual void __fastcall DrawScene(void); virtual void __fastcall SetupSurfaces(System::TObject* Sender); virtual TSpecialRect __fastcall GetRect(int Col, int Row) = 0; virtual bool __fastcall MoveGrid(int Col, int Row, bool CallFlip) = 0; public: __fastcall virtual THermesTiler(Classes::TComponent* AOwner); virtual void __fastcall DestroyObjects(void); Windows::TRect __fastcall MapTypeToTileRect(int MapType); virtual long __fastcall RestoreSurfaces(void); __published: __property System::AnsiString TileMap = {read=FTileMap, write=FTileMap, nodefault}; __property int BitmapWidth = {read=FBitmapWidth, write=FBitmapHeight, nodefault}; __property int BitmapHeight = {read=FBitmapHeight, write=FBitmapHeight, nodefault}; }; typedef void __fastcall (__closure *TMoveHeroEvent)(System::TObject* Sender, const tagPOINT &NewPos, int NewType, bool &MoveOk); class THermesChart : public THermesTiler { typedef THermesTiler inherited; private: Creatures1::TCreature* FHero; bool FHeroActive; TMoveHeroEvent FOnHeroMove; bool __fastcall CheckHeroPos(Creatures1::TCreatureList* HeroList, int Col, int Row); Windows::TRect __fastcall MapTypeToCreature(int Col, int Row); virtual TSpecialRect __fastcall GetRect(int Col, int Row); virtual bool __fastcall MoveGrid(int Col, int Row, bool CallFlip); protected: void __fastcall MoveHero(int NewCol, int NewRow); virtual void __fastcall SetupSurfaces(System::TObject* Sender); public: __fastcall virtual ~THermesChart(void); void __fastcall Move(int Value); __published: __property bool HeroActive = {read=FHeroActive, write=FHeroActive, nodefault}; __property TMoveHeroEvent OnHeroMove = {read=FOnHeroMove, write=FOnHeroMove}; public: __fastcall virtual THermesChart(Classes::TComponent* AOwner) : Mercury2::THermesTiler(AOwner) { } }; class TSprite; class TSprite : public Classes::TComponent { typedef Classes::TComponent inherited; friend TSpriteScene; private: System::AnsiString FBitmap; tagPOINT FPosition; IDirectDrawSurface* FSurface; RECT FRect; public: bool __fastcall IsHit(int X, int Y); __property IDirectDrawSurface* Surface= {read=FSurface, write=FSurface, nodefault}; __property RECT Rect = {read=FRect, write=FRect}; __published: __property System::AnsiString Bitmap={read=FBitmap, write=FBitmap, nodefault}; __property long XPos = {read=FPosition.x, write=FPosition.x, nodefault}; __property long YPos = {read=FPosition.y, write=FPosition.y, nodefault}; public: __fastcall virtual TSprite(Classes::TComponent* AOwner) : Classes::TComponent(AOwner) { } __fastcall virtual ~TSprite(void) { } }; class TSpriteScene : public TScene { typedef TScene inherited; private: Classes::TList* FSpriteList; protected: __fastcall virtual ~TSpriteScene(void); virtual void __fastcall SetupSurfaces(System::TObject* Sender); public: __fastcall virtual TSpriteScene(Classes::TComponent* AOwner); virtual void __fastcall DestroyObjects(void); void __fastcall AddSprite(TSprite* Sprite); virtual void __fastcall DrawScene(void); virtual long __fastcall RestoreSurfaces(void); __property Classes::TList* SpriteList = {read=FSpriteList, write=FSpriteList,nodefault}; __published: __property TransparentColor; }; //////////////////////////////////////// // TSceneEditor //////////////////////// //////////////////////////////////////// class TSceneEditor: public TComponentEditor { protected: virtual __fastcall void Edit(void); public: virtual __fastcall TSceneEditor(TComponent *AOwner, TFormDesigner *Designer) : TComponentEditor(AOwner, Designer) {} }; //-- var, const, procedure -------------------------------------------------- #define Timer1 (Byte)(1) extern void __fastcall Register(void); }/* namespace Mercury1 */ #if !defined(NO_IMPLICIT_NAMESPACE_USE) using namespace Mercury2; #endif //-- end unit ----------------------------------------------------------------
#endif // Mercury2
/////////////////////////////////////// // Mercury2.cpp // DirectX Graphics // Copyright (c) 1997 by Charlie Calvert // Thanks to John Thomas, Stuart Fullmar and Jeff Cottingham /////////////////////////////////////// #include <vcl\vcl.h> #pragma hdrstop #include "Mercury2.h" #include "errors1.h" #include "SceneEditor1.h" #pragma link "Errors1.obj" #pragma link "SceneEditor1.obj" /*{ ------------------------ } { --- THermes ------------ } { ------------------------ } */ THermes *AHermes; void Timer2Timer(HWND H, UINT Msg, UINT Event, DWORD Time) { if (AHermes->Active) AHermes->Flip(); } namespace Mercury2 { // Slightly modified version of code from DDUtils.cpp HRESULT DDCopyBitmap(IDirectDrawSurface *pdds, HBITMAP hbm, int x, int y, int dx, int dy) { HDC hdcImage; HDC hdc; BITMAP bm; DDSURFACEDESC ddsd; HRESULT hr; if (hbm == NULL || pdds == NULL) return E_FAIL; // // make sure this surface is restored. // pdds->Restore(); // // select bitmap into a memoryDC so we can use it. // hdcImage = CreateCompatibleDC(NULL); if (!hdcImage) OutputDebugString("createcompatible dc failed\n"); SelectObject(hdcImage, hbm); // // get size of the bitmap // GetObject(hbm, sizeof(bm), &bm); // get size of bitmap dx = dx == 0 ? bm.bmWidth : dx; // use the passed size, unless zero dy = dy == 0 ? bm.bmHeight : dy; // // get size of surface. // ddsd.dwSize = sizeof(ddsd); ddsd.dwFlags = DDSD_HEIGHT | DDSD_WIDTH; pdds->GetSurfaceDesc(&ddsd); if ((hr = pdds->GetDC(&hdc)) == DD_OK) { StretchBlt(hdc, 0, 0, ddsd.dwWidth, ddsd.dwHeight, hdcImage, x, y, dx, dy, SRCCOPY); pdds->ReleaseDC(hdc); } DeleteDC(hdcImage); return hr; } // Slightly modified version of code from DDUtils.cpp void DDReloadBitmapLib(HANDLE Lib, IDirectDrawSurface *Surface, const AnsiString BitmapName) { HBITMAP Bitmap; if (Lib) Bitmap = LoadImage(Lib, BitmapName.c_str(), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION); else Bitmap = LoadImage(GetModuleHandle(NULL), BitmapName.c_str(), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION); if (!Bitmap) Bitmap = LoadImage(0, BitmapName.c_str(), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_CREATEDIBSECTION); if (!Bitmap) throw Exception("Unable to load bitmap %s", OPENARRAY(TVarRec, (BitmapName))); DDCopyBitmap(Surface, Bitmap, 0, 0, 0, 0); DeleteObject(Bitmap); } // Slightly modified version of code from DDUtils.cpp IDirectDrawSurface *DDCreateSurface(IDirectDraw *DD, DWORD Width, DWORD Height, bool SysMem, bool Trans, DWORD dwColorKey) { DDSURFACEDESC SurfaceDesc; HRESULT hr; DDCOLORKEY ColorKey; IDirectDrawSurface *Surface; // fill in surface desc memset(&SurfaceDesc, 0, sizeof(DDSURFACEDESC)); SurfaceDesc.dwSize = sizeof(SurfaceDesc); SurfaceDesc.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH; SurfaceDesc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; SurfaceDesc.dwHeight = Height; SurfaceDesc.dwWidth = Width; hr = DD->CreateSurface(&SurfaceDesc, &Surface, NULL); // set the color key for this bitmap if (hr == DD_OK) { if (Trans) { ColorKey.dwColorSpaceLowValue = dwColorKey; ColorKey.dwColorSpaceHighValue = dwColorKey; Surface->SetColorKey(DDCKEY_SRCBLT, &ColorKey); } } else throw EDDError("CreateSurface Failed in DDCreateSurface"); return Surface; } // DDCreateSurface IDirectDrawClipper *CreateClipper(IDirectDraw *DD, HWND Handle) { HRESULT hr; IDirectDrawClipper *lpClipper; hr = DD->CreateClipper(0, &lpClipper, NULL); if (hr != DD_OK) { throw EDDError("No Clipper"); } hr = lpClipper->SetHWnd(0, Handle); if (hr != DD_OK) { throw EDDError("Can''t set clipper window handle"); } return lpClipper; } } __fastcall THermes::THermes(TComponent* AOwner) :TComponent(AOwner) { FHandle = ((TWinControl *)(AOwner))->Handle; FFirstTime = true; }// THermes void __fastcall THermes::EndExclusive() { FDirectDraw->FlipToGDISurface(); if (FExclusive) FDirectDraw->SetCooperativeLevel(FHandle, DDSCL_NORMAL); }// EndExclusive void __fastcall THermes::ErrorEvent(String S) { FActive = false; EndExclusive(); throw EDDError(S); }// ErrorEvent void __fastcall THermes::Run() { InitObjects(); Flip(); }// Run void __fastcall THermes::DDTest(long hr, System::AnsiString S) { if (!Windows::Succeeded(hr)) throw EDDError("DDTest Error: %s $%x %s", OPENARRAY(TVarRec, (S, int(hr), AnsiString(GetOleError(hr))))); }// DDTest ///////////////////////////////////////////////// // Create the primary surface ///////////////////////////////////////////////// bool __fastcall THermes::CreatePrimary(void) { DDSURFACEDESC SurfaceDesc; HResult hr; bool Result = true; memset(&SurfaceDesc, 0, sizeof(SurfaceDesc)); SurfaceDesc.dwSize = sizeof(SurfaceDesc); if (!FExclusive){ SurfaceDesc.dwFlags = DDSD_CAPS; SurfaceDesc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE; } else{ SurfaceDesc.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT; SurfaceDesc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_COMPLEX; SurfaceDesc.dwBackBufferCount = 1; }; hr = FDirectDraw->CreateSurface(&SurfaceDesc, &FPrimarySurface, NULL); if (hr != DD_OK) throw EDDError("THermes.CreatePrimary: %d %s", OPENARRAY(TVarRec, (int(hr), GetOleError(hr)))); else return Result; }// CreatePrimary void __fastcall THermes::SetScene(TScene *Scene) { if (Scene) Scene->DestroyObjects(); FScene = Scene; }// SetScene bool __fastcall THermes::SetUpBack(void) { bool Result = false; HResult hr; DDSCAPS DDSCaps; if (!FExclusive) { FBackSurface = Mercury2::DDCreateSurface(FDirectDraw, 640, 480, false, false, 0); if (FBackSurface == NULL) throw EDDError("Can''t set up back surface"); FClipper = Mercury2::CreateClipper(FDirectDraw, FHandle); hr = FPrimarySurface->SetClipper(FClipper); if( hr != DD_OK ) throw EDDError("Can''t attach clipper to front buffer"); } else { memset(&DDSCaps, 0, sizeof(DDSCaps)); DDSCaps.dwCaps = DDSCAPS_BACKBUFFER; hr = FPrimarySurface->GetAttachedSurface(&DDSCaps, &FBackSurface); if (hr != DD_OK) throw EDDError("TSpeedDraw.SetUpBack: %d %s", OPENARRAY(TVarRec, (int(hr), GetOleError(hr)))); else Result = true; }; return Result; }// SetUpBack void __fastcall THermes::InitBaseObjects() { DWORD Flags; HResult hr; DDTest(DirectDrawCreate(NULL, &FDirectDraw, NULL), "InitObjects1"); if (!FExclusive) Flags = DDSCL_NORMAL; else Flags = DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN; DDTest(FDirectDraw->SetCooperativeLevel(FHandle, Flags),"SetCooperativeLevel"); if (FExclusive) { hr = FDirectDraw->SetDisplayMode(640, 480, 8); if(hr != DD_OK) throw EDDError("TSpeedDraw.InitObjects: %d %s", OPENARRAY(TVarRec, (int(hr), GetOleError(hr)))); }; CreatePrimary(); SetUpBack(); if (FCreatureList != NULL) FCreatureList->ReadFiles(); }// InitBaseObjects /* Here are the steps in the initialization: Create DirectDraw Object SetCooperativeLevel if Exclusive then SetDisplayMode CreatePrimary SetupBack Create the work surface Set Active to true */ void __stdcall THermes::InitObjects(void) { AHermes = this; if (FFirstTime) { InitBaseObjects(); FFirstTime = false; }; if ((Scene) && (Scene->FBackgroundBitmap != "")) Scene->SetupSurfaces(this); if (FUseTimer) SetTimer(FHandle, Timer1, FTimerInterval, (FARPROC)Timer2Timer); FActive = true; }// InitObjects void __fastcall THermes::DrawBitmaps() { if (Scene) Scene->DrawScene(); } bool __fastcall THermes::MakeItSo(HResult DDResult) { bool Result; switch(DDResult) { case DD_OK: Result = true; break; case DDERR_SURFACELOST: Result = (RestoreSurfaces() == DD_OK); break; default: Result = DDResult != DDERR_WASSTILLDRAWING; break; }; return Result; } // MakeItSo long __fastcall THermes::RestoreSurfaces() { HRESULT Result; Result = FPrimarySurface->Restore(); if ((Result == DD_OK) && (Scene)) Result = Scene->RestoreSurfaces(); return Result; }// RestoreSurfaces void __fastcall THermes::Flip(void) { RECT R1, R; FTimerOdd = !FTimerOdd; if (!FActive) return; if (!FExclusive) { try { DrawBitmaps(); GetWindowRect(FHandle, &R); R1 = Rect(0, 0, 640, 480); DDTest(FPrimarySurface->Blt(&R, FBackSurface, &R1, 0, NULL), "Flip"); } catch(Exception &E) { ErrorEvent("Flipping"); }; } else { try { if (FActive) { do { Application->ProcessMessages(); } while(!MakeItSo(FPrimarySurface->Flip(NULL, DDFLIP_WAIT))); DrawBitmaps(); } } catch(...) { ErrorEvent("Flipping"); } } }// Flip /////////////////////////////////////// // TDraw ////////////////////////////// /////////////////////////////////////// __fastcall TDraw::TDraw(Classes::TComponent* AOwner) : TComponent(AOwner) { } __fastcall TDraw::~TDraw(void) { if (FLib != NULL) FreeLibrary(FLib); } bool __fastcall TDraw::CreateDDSurface(IDirectDrawSurface* &DDS, System::AnsiString BitmapName, bool UsePalette) { bool Result; DDCOLORKEY ColorKey; HRESULT hr; HANDLE Dib; if (UsePalette) { FPalette = LoadPalette(FLib, BitmapName.c_str()); if (!FPalette) throw EDDError("LoadPalette Failed"); FHermes->FPrimarySurface->SetPalette(FPalette); } Dib = GetDib(FLib, BitmapName); DDS = CreateSurface(Dib); if (!DDS) throw EDDError("CreateSurface Failed"); ColorKey.dwColorSpaceLowValue = FTransparentColor; ColorKey.dwColorSpaceHighValue = FTransparentColor; hr = DDS->SetColorKey(DDCKEY_SRCBLT, &ColorKey); if (hr != DD_OK) throw EDDError("TSpeedDraw.CreateDDSurface: %d %s", OPENARRAY(TVarRec, (int(hr), GetOleError(hr)))); else Result = True; return Result; } IDirectDrawSurface* __fastcall TDraw::CreateSurface(HANDLE Bitmap) { DDSURFACEDESC SurfaceDesc; Windows::TBitmap BM; IDirectDrawSurface *Result; if (Bitmap == 0) throw Exception("No Bitmap in CreateSurface"); try { GetObject(Bitmap, sizeof(BM), &BM); memset(&SurfaceDesc, 0, sizeof(SurfaceDesc)); SurfaceDesc.dwSize = sizeof(SurfaceDesc); SurfaceDesc.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH; SurfaceDesc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; SurfaceDesc.dwWidth = BM.bmWidth; SurfaceDesc.dwHeight = BM.bmHeight; if (FHermes->FDirectDraw->CreateSurface(&SurfaceDesc, &Result, NULL) != DD_OK) throw Exception("CreateSurface failed"); DDCopyBitmap(Result, Bitmap, 0, 0, 0, 0); } catch(Exception &E) { FHermes->ErrorEvent("TSpeedDraw.CreateSurface: " + E.Message); } return Result; } HANDLE __fastcall TDraw::GetDib(HANDLE Instance, System::AnsiString S) { HANDLE Result; UINT Flags; if (Instance != 0) Flags = LR_CREATEDIBSECTION; else Flags = LR_LOADFROMFILE | LR_CREATEDIBSECTION; Result = LoadImage((HANDLE)Instance, S.c_str(), IMAGE_BITMAP, 0, 0, Flags); if (Result == 0) FHermes->ErrorEvent("TSpeedDraw.GetDib: Could not load bitmap"); return Result; } IDirectDrawPalette* __fastcall TDraw::LoadPalette(HANDLE Instance, const System::AnsiString BitmapName) { IDirectDrawPalette* ddpal; int i; int n; int fh; HRSRC h; LPBITMAPINFO BitmapInfo; PALETTEENTRY ape[256]; RGBQUAD * RGBQuad; // // build a 332 palette as the default. // for (i=0; i<256; i++) { ape[i].peRed = (BYTE)(((i >> 5) & 0x07) * 255 / 7); ape[i].peGreen = (BYTE)(((i >> 2) & 0x07) * 255 / 7); ape[i].peBlue = (BYTE)(((i >> 0) & 0x03) * 255 / 3); ape[i].peFlags = (BYTE)0; } if (BitmapName == "") FHermes->ErrorEvent("No bitmapname in LoadPalette"); // // get a pointer to the bitmap resource. // if (Instance) { h = FindResource(Instance, BitmapName.c_str(), RT_BITMAP); if (h) { BitmapInfo = (LPBITMAPINFO)LockResource(LoadResource(Instance, h)); if (!BitmapInfo) throw EDDError("No LockResouce: " + this->ClassName()); RGBQuad = (RGBQUAD*)BitmapInfo->bmiColors; if (BitmapInfo == NULL || BitmapInfo->bmiHeader.biSize <sizeof(BITMAPINFOHEADER)) n = 0; else if (BitmapInfo->bmiHeader.biBitCount > 8) n = 0; else if (BitmapInfo->bmiHeader.biClrUsed == 0) n = 1 << BitmapInfo->bmiHeader.biBitCount; else n = BitmapInfo->bmiHeader.biClrUsed; // // a DIB color table has its colors stored BGR not RGB // so flip them around. // for(i=0; i<n; i++ ) { ape[i].peRed = RGBQuad[i].rgbRed; ape[i].peGreen = RGBQuad[i].rgbGreen; ape[i].peBlue = RGBQuad[i].rgbBlue; ape[i].peFlags = 0; } } } else { fh = _lopen(BitmapName.c_str(), OF_READ); if (fh != -1) { BITMAPFILEHEADER bf; BITMAPINFOHEADER bi; _lread(fh, &bf, sizeof(bf)); _lread(fh, &bi, sizeof(bi)); _lread(fh, ape, sizeof(ape)); _lclose(fh); if (bi.biSize != sizeof(BITMAPINFOHEADER)) n = 0; else if (bi.biBitCount > 8) n = 0; else if (bi.biClrUsed == 0) n = 1 << bi.biBitCount; else n = bi.biClrUsed; // // a DIB color table has its colors stored BGR not RGB // so flip them around. // for(i=0; i<n; i++ ) { BYTE r = ape[i].peRed; ape[i].peRed = ape[i].peBlue; ape[i].peBlue = r; } } } FHermes->FDirectDraw->CreatePalette(DDPCAPS_8BIT, ape, &ddpal, NULL); return ddpal; } void __fastcall TDraw::WriteXY(int X, int Y, System::AnsiString S) { HDC DC; FHermes->BackSurface->GetDC(&DC); SetBkMode(DC, TRANSPARENT); TextOut(DC, X, Y, S.c_str(), S.Length()); FHermes->BackSurface->ReleaseDC(DC); } // ------------------------ // -- TScene -------------- // ------------------------ __fastcall TScene::TScene(TComponent *AOwner) : TDraw(AOwner) { ShowBitmap = true; } void __fastcall TScene::DestroyObjects(void) { if(FWorkSurface != NULL) FWorkSurface->Release(); FWorkSurface = NULL; } long _fastcall TScene::RestoreSurfaces(void) { long Result; Result = FWorkSurface->Restore(); if(Result == DD_OK) DDReloadBitmapLib(FLib, FWorkSurface, FBackgroundBitmap); return(Result); } void __fastcall TScene::SetupSurfaces(System::TObject *Sender) { AnsiString ErrStr = "TSpeedDraw.SetupWorkSurface: No Surface Desc %d %s"; DDSURFACEDESC SurfaceDesc; HRESULT hr; FHermes = dynamic_cast <THermes *>(Sender); if(FDLLName != "") { if(!FLib) FLib = LoadLibrary(FDLLName.c_str()); if(int(FLib) < 32) throw EDDError("No Library"); } if(!CreateDDSurface(FWorkSurface, FBackgroundBitmap, True)) throw EDDError("TSpeedDraw.SetupWorkSurface: No WorkSurface: " + FBackgroundBitmap); else { SurfaceDesc.dwSize = sizeof(SurfaceDesc); hr = FWorkSurface->GetSurfaceDesc(&SurfaceDesc); if(hr != DD_OK) throw EDDError(ErrStr, OPENARRAY(TVarRec, (int(hr), GetOleError(hr)))); FBackRect = Rect(0, 0, SurfaceDesc.dwWidth, SurfaceDesc.dwHeight); } if(FOnSetupSurfaces) FOnSetupSurfaces(this); } void _fastcall TScene::DrawScene() { AnsiString ErrStr = "TSpeedDraw.BackGroundBlits: $%x \r %s \r %s"; HRESULT hr; HDC DC; HBRUSH OldBrush, Brush; if (FBlankScene) { FHermes->BackSurface->GetDC(&DC); // Don't step through!! Brush = CreateSolidBrush(FBackColor); OldBrush = SelectObject(DC, Brush); Rectangle(DC, 0, 0, 640, 480); SelectObject(DC, OldBrush); DeleteObject(Brush); FHermes->BackSurface->ReleaseDC(DC); } if (FShowBitmap) { hr = AHermes->BackSurface->BltFast(FBackOrigin.x, FBackOrigin.y, FWorkSurface, &FBackRect, DDBLTFAST_WAIT | DDBLTFAST_SRCCOLORKEY); if (!Windows::Succeeded(hr)) throw EDDError(ErrStr, OPENARRAY(TVarRec, (int(hr), GetOleError(hr), "Check BackRect, BackOrigin???"))); } if (FOnDrawScene) FOnDrawScene(this); } /////////////////////////////////////// // THermesTiler /////////////////////// /////////////////////////////////////// __fastcall THermesTiler::THermesTiler(Classes::TComponent* AOwner) :TScene(AOwner) { FBitmapWidth = 32; FBitmapHeight = 32; FMaxMapRows = 12; FMaxMapCols = 20; } __fastcall THermesTiler::~THermesTiler(void) { DestroyObjects(); } void __fastcall THermesTiler::DrawScene(void) { TScene::DrawScene(); int i, j, k; TSpecialRect SpR; if (FTileSurface == NULL) return; k = 1; for (j = 1; j <= FMaxMapRows; j++) for (i = 1; i <= FMaxMapCols; i++) { SpR = GetRect(i + FHermes->CreatureList->MapCol, j + FHermes->CreatureList->MapRow); FHermes->BackSurface->BltFast(FBitmapWidth * (k - 1), FBitmapHeight * (j - 1), FTileSurface, &SpR.R1, DDBLTFAST_SRCCOLORKEY); if (SpR.IsCreature) // There's a character or building here FHermes->BackSurface->BltFast(FBitmapWidth * (k - 1), FBitmapHeight * (j - 1), FTileSurface, &SpR.R2, DDBLTFAST_SRCCOLORKEY); if ((i % 20) == 0) k = 1; else k++; } } void __fastcall THermesTiler::SetupSurfaces(System::TObject* Sender) { TScene::SetupSurfaces(Sender); if (FTileMap != "") if (!CreateDDSurface(FTileSurface, FTileMap, True)) throw Exception("THermesTiler.InitObjects"); } void __fastcall THermesTiler::DestroyObjects(void) { if (FTileMap != "") if (FTileSurface != NULL) FTileSurface->Release(); FTileSurface = NULL; } Windows::TRect __fastcall THermesTiler::MapTypeToTileRect(int MapType) { int X, Y = 0; if ((MapType > 19) && (MapType < 41)) { Y = FBitmapHeight; X = (MapType - 21) * FBitmapWidth; } else if (MapType > 39) { Y = 64; X = (MapType - 41) * FBitmapWidth; } else { X = MapType * FBitmapWidth; } return Rect(X, Y, X + FBitmapWidth, FBitmapHeight + Y); } long __fastcall THermesTiler::RestoreSurfaces(void) { long Result = TScene::RestoreSurfaces(); if (Result == DD_OK) { Result = FTileSurface->Restore(); if (Result == DD_OK) if (FTileMap != "") DDReloadBitmapLib(FLib, FTileSurface, FTileMap); } return Result; } /////////////////////////////////////// // THermesChart /////////////////////// /////////////////////////////////////// bool __fastcall THermesChart::CheckHeroPos(Creatures1::TCreatureList* HeroList, int Col, int Row) { int NewPosType; TPoint NewPos; bool MoveOk = True; if (OnHeroMove != NULL) { NewPos.x = FHero->TrueCol + Col; NewPos.y = FHero->TrueRow + Row; NewPosType = HeroList->GetMapType(NewPos.x, NewPos.y); OnHeroMove(this, NewPos, NewPosType, MoveOk); } return MoveOk; } Windows::TRect __fastcall THermesChart::MapTypeToCreature(int Col, int Row) { TCreature *Creature = FHermes->CreatureList->CreatureFromLocation(Col, Row); if (Creature->TypeStr == "Hero") return MapTypeToTileRect(3); else return MapTypeToTileRect(StrToInt(Creature->ID)); } TSpecialRect __fastcall THermesChart::GetRect(int Col, int Row) { Byte MapType; TSpecialRect Result; MapType = FHermes->CreatureList->Map[Col][Row]; if ((FHermes->FTimerOdd == True) && (MapType == 1 /*mtWater*/)) MapType = 4; // mtWater2; if (BitOn(7, MapType)) { Result.IsCreature = True; SetBit(7, 0, MapType); Result.R1 = MapTypeToTileRect(MapType); Result.R2 = MapTypeToCreature(Col, Row); } else { Result.IsCreature = False; Result.R1 = MapTypeToTileRect(MapType); } return Result; } bool __fastcall THermesChart::MoveGrid(int Col, int Row, bool CallFlip) { TPoint P; if (CheckHeroPos(FHermes->CreatureList, Col, Row)) { P.x = FHermes->CreatureList->MapCol + Col; P.y = FHermes->CreatureList->MapRow + Row; if ((P.x < 0) || (P.x > (FHermes->CreatureList->MaxCols - FMaxMapCols)) || (P.y < 0) || (P.y > (FHermes->CreatureList->MaxRows - FMaxMapRows))) return False; FHermes->CreatureList->MapCol = P.x; FHermes->CreatureList->MapRow = P.y; FHermes->CreatureList->MoveCreature(FHero, Col, Row); FHermes->Flip(); return True; } return False; } __fastcall THermesChart::~THermesChart(void) { } void __fastcall THermesChart::MoveHero(int NewCol, int NewRow) { TPoint P; if (!CheckHeroPos(FHermes->CreatureList, NewCol, NewRow)) return; P.x = FHero->ScreenCol + NewCol; P.y = FHero->ScreenRow + NewRow; if ((P.x <= 0) || (P.x > (FMaxMapCols)) || (P.y <= 1) || (P.y > FMaxMapRows)) return; FHero->ScreenCol = P.x; FHero->ScreenRow = P.y; FHermes->CreatureList->MoveCreature(FHero, NewCol, NewRow); FHermes->Flip(); } void __fastcall THermesChart::SetupSurfaces(System::TObject* Sender) { THermesTiler::SetupSurfaces(Sender); if (FHermes->CreatureList == NULL) throw EDDError("CreatureList cannot be blank."); FHeroActive = True; FHero = FHermes->CreatureList->CreatureFromName("Hero"); } void __fastcall THermesChart::Move(int Value) { if (Value == VK_INSERT) HeroActive = !HeroActive; else if (HeroActive) switch (Value) { case VK_RIGHT: MoveHero(1, 0); break; case VK_LEFT: MoveHero(-1, 0); break; case VK_DOWN: MoveHero(0, 1); break; case VK_UP: MoveHero(0, -1); break; } else switch(Value) { case VK_RIGHT: MoveGrid(1, 0, False); break; case VK_LEFT: MoveGrid(-1, 0, False); break; case VK_DOWN: MoveGrid(0, 1, False); break; case VK_UP: MoveGrid(0, -1, False); break; } } /* ------------------------ */ /* -- TSprite ------------- */ /* ------------------------ */ bool __fastcall TSprite::IsHit(int X, int Y) { X = X - XPos; Y = Y - YPos; if ((X >= 0) && (Y >= 0) && (X <= Rect.right) && (Y <= Rect.bottom)) return true; else return false; } /* ------------------------ */ /* -- TSpriteScene -------- */ /* ------------------------ */ __fastcall TSpriteScene::TSpriteScene(TComponent *AOwner) : TScene(AOwner) { FSpriteList = new TList(); } __fastcall TSpriteScene::~TSpriteScene() { DestroyObjects(); delete FSpriteList; } void __fastcall TSpriteScene::DestroyObjects(void) { if(FSpriteList != NULL) { for(int i = 0; i < FSpriteList->Count; i++) ((TSprite*)(FSpriteList->Items[i]))->Surface->Release(); FSpriteList->Clear(); } } void __fastcall TSpriteScene::AddSprite(TSprite *Sprite) { SpriteList->Add(Sprite); } void __fastcall TSpriteScene::DrawScene(void) { const char ErrStr[] = "TSpriteScene.DrawScene"; HResult hr; TSprite *Sprite; TScene::DrawScene(); for(int i = 0; i < FSpriteList->Count; i++) { Sprite = (TSprite *)FSpriteList->Items[i]; hr = FHermes->BackSurface->BltFast(Sprite->XPos, Sprite->YPos, Sprite->Surface, &Sprite->FRect, DDBLTFAST_WAIT | DDBLTFAST_SRCCOLORKEY); if(!Windows::Succeeded(hr)) throw(EDDError(ErrStr, OPENARRAY(TVarRec, (int(hr), GetOleError(hr), "Check BackRect, BackOrigin???")))); } } long __fastcall TSpriteScene::RestoreSurfaces(void) { TSprite *Sprite; HRESULT Result; Result = TScene::RestoreSurfaces(); if(Result == DD_OK) { for(int i = 0; i < FSpriteList->Count; i++) { Sprite = (TSprite *)FSpriteList->Items[i]; Result = Sprite->Surface->Restore(); if(Result == DD_OK) DDReloadBitmapLib(FLib, Sprite->Surface, Sprite->Bitmap); else break; // Exit on error } } return((long)Result); } void __fastcall TSpriteScene::SetupSurfaces(TObject *Sender) { IDirectDrawSurface *Surface; TSprite *Sprite; DDSURFACEDESC SurfaceDesc; HRESULT hr; TScene::SetupSurfaces(Sender); if(SpriteList == NULL) return; for(int i = 0; i < SpriteList->Count; i++) { Sprite = (TSprite *)SpriteList->Items[i]; if(!CreateDDSurface(Surface, Sprite->Bitmap, False)) throw EDDError("Could not create surface"); SurfaceDesc.dwSize = sizeof(SurfaceDesc); hr = Surface->GetSurfaceDesc(&SurfaceDesc); if(hr == DD_OK) Sprite->Rect = Rect(0, 0, SurfaceDesc.dwWidth, SurfaceDesc.dwHeight); else throw EDDError("No SurfaceDesc"); Sprite->Surface = Surface; } } void __fastcall TSceneEditor::Edit(void) { RunSceneEditorDlg((TScene *)Component); } namespace Mercury2 { void __fastcall Register() { TComponentClass classes[5] = {__classid(THermes), __classid(TScene), __classid(THermesChart), __classid(TSpriteScene), __classid(TSprite)}; RegisterComponents("Unleash", classes, 4); RegisterComponentEditor(__classid(TScene), __classid(TSceneEditor)); }
}
In this chapter, you learned how to create a simple graphics engine that gives you access to DirectX. In particular, you learned how to do the following:
In the next chapter, I add a simple game engine to this graphics engine and then give a simple example of how to use these tools to create the elements of a strategy game.
©Copyright, Macmillan Computer Publishing. All rights reserved.